/*
 * EjudgeLogonService.cpp
 *
 *  Created on: Dec 24, 2011
 *      Author: andy
 */

#include "EjudgeLogonService.h"

#include "boost/property_tree/ptree.hpp"
#include "boost/property_tree/ini_parser.hpp"

extern "C" {
	#include "userlist_clnt.h"
	#include "userlist.h"
	#include "userlist_proto.h"
	#include "xml_utils.h"
}

class EjudgeClnt {
public:
	EjudgeClnt(const std::string& socket_path);
	void privLogin(const LogonEntry& logon_entry);
	LogonResult userLogin(const LogonEntry& logon_entry);
	LogonResult masterLogin(const LogonEntry& logon_entry);
private:
	userlist_clnt_t clnt;
};

void Settings::load(const std::string& filename)
{
	using boost::property_tree::ptree;
	ptree pt;

	boost::property_tree::ini_parser::read_ini(filename, pt);

	login_ = pt.get<std::string>("ejudge.login");
	password_ = pt.get<std::string>("ejudge.password");
	ejudge_socket_path_ = pt.get<std::string>("ejudge.socket_path");
	log_path_ = pt.get<std::string>("logging.log_path");
    out_socket_path_ = pt.get<std::string>("general.socket_path");
    session_duration_ = boost::posix_time::seconds(pt.get<int>("general.session_duration"));
    max_contests_count_ = pt.get<int>("general.max_contests_count");
}

EjudgeLogonService::EjudgeLogonService(Settings &settings) :
    contest_logons(settings.max_contests_count_),
    priv_contest_logons(settings.max_contests_count_),
    session_duration_(settings.session_duration_)
{
	LogonEntry logon_entry(0, settings.login_, settings.password_, std::string("127.0.0.1"));
	clnt.reset(new EjudgeClnt(settings.ejudge_socket_path_));
	clnt->privLogin(logon_entry);
}

EjudgeLogonService::~EjudgeLogonService() {
	// TODO Auto-generated destructor stub
}

void EjudgeClnt::privLogin(const LogonEntry& logon_entry)
{
	ej_ip_t ip_address;
	if (xml_parse_ip(NULL, 0, 0, -1, (const unsigned char*)logon_entry.ip_.c_str(), &ip_address) < 0) {
		throw EjudgeLogonServiceException("Ejudge socket connections error");
	}

	int uid, priv_level_out;
	ej_cookie_t sid;
	unsigned char *name;

	if (userlist_clnt_priv_login(
			clnt,
			ULS_PRIV_LOGIN,
			ip_address, 0, 0, 0, USER_ROLE_ADMIN,
			(const unsigned char*)logon_entry.login_.c_str(),
			(const unsigned char*)logon_entry.password_.c_str(),
			&uid, &sid, &priv_level_out, &name) < 0) {
		throw EjudgeLogonServiceException("Ejudge userlist_clnt_priv_login error");
	}
}

EjudgeClnt::EjudgeClnt(const std::string& socket_path)
{
	clnt = userlist_clnt_open(socket_path.c_str());
	if (!clnt) {
		throw EjudgeLogonServiceException("Ejudge socket connections error");
	}
}

LogonResult EjudgeLogonService::getMasterLogon(const LogonEntry& entry)
{
    LogonResult  res;
    boost::lock_guard<boost::mutex> lock(m);

    if (entry.contest_id_ >= priv_contest_logons.size()) {
        throw EjudgeLogonServiceException("Contest id out of range");
    }

    LogonMapEntry tmp = priv_contest_logons[entry.contest_id_];
    std::cerr << "Master logon " <<
            entry.contest_id_ << " " <<
            entry.login_ << " " <<
            entry.user_id_ << " " << std::endl;
    if (tmp.user_id_ == 0 || tmp.time_ < posix_time::second_clock::local_time() - session_duration_) {
        res = clnt->masterLogin(entry);
        if (res.sid_ == "") {
            throw EjudgeLogonServiceException(res.name_);
        }
        std::cerr << "Master do logon " << res.user_id_ << std::endl;
        priv_contest_logons[entry.contest_id_] = LogonMapEntry(res.user_id_, res.sid_, posix_time::second_clock::local_time());
    } else {
        res.sid_ = tmp.sid_;
        res.user_id_ = tmp.user_id_;
    }
    return res;
}

LogonResult EjudgeLogonService::getLogon(const LogonEntry& entry)
{
    LogonResult res;
	boost::lock_guard<boost::mutex> lock(m);

    if (entry.contest_id_ >= contest_logons.size()) {
        throw EjudgeLogonServiceException("Contest id out of range");
    }

	logon_container &c = contest_logons[entry.contest_id_];

    auto it = c.find(entry.user_id_);
    bool must_logon = false;

    if (it != c.end()) {
        if (it->time_ < posix_time::second_clock::local_time() - session_duration_)
        {
            c.erase(it);
            must_logon = true;
        } else {
            res.sid_ = it->sid_;
            res.user_id_ = entry.user_id_;
        }
    } else {
        must_logon = true;
    }

    if (must_logon) {
        std::cerr << "logon" << std::endl;
        res = clnt->userLogin(entry);
        if (res.sid_ == "") {
            throw EjudgeLogonServiceException(res.name_);
        }
        c.insert(LogonMapEntry(res.user_id_, res.sid_, posix_time::second_clock::local_time()));
    }

    cleanUp(c);

	return res;
}

void EjudgeLogonService::cleanUp(logon_container &c)
{
    auto it = c.get<1>().begin();
    int count = 5;
    while (it != c.get<1>().end() && it->time_ < posix_time::second_clock::local_time() - session_duration_ && count > 0) {
        ++it;
        --count;
    }
    if (it != c.get<1>().begin()) {
        std::cerr << "delete " << count << std::endl;

    }
    c.get<1>().erase(c.get<1>().begin(), it);
}

LogonResult EjudgeClnt::userLogin(const LogonEntry& logon_entry)
{
    ej_ip_t ip_address;
    if (xml_parse_ip(NULL, 0, 0, -1, (const unsigned char*)logon_entry.ip_.c_str(), &ip_address) < 0) {
        throw EjudgeLogonServiceException("Ejudge socket connections error");
    }

    int uid;
    ej_cookie_t sid;
    unsigned char *name;

    int ret;

    if ((ret = userlist_clnt_login(
            clnt,
            ULS_TEAM_CHECK_USER,
            ip_address,
            0,
            logon_entry.contest_id_,
            1,
            (const unsigned char*)logon_entry.login_.c_str(),
            (const unsigned char*)logon_entry.password_.c_str(),
            &uid,
            &sid,
            &name
    )) < 0)
    {
        char tmp[10];
        sprintf(tmp, "%d", ret);
        std::string err = "userlist_clnt_login: " + std::string(tmp);
        return LogonResult(err, 0, "");
    }
    char buf[256];
    sprintf(buf, "%llx", sid);
    LogonResult res(std::string((char *)name), uid, buf);

    return res;
}

LogonResult EjudgeClnt::masterLogin(const LogonEntry& logon_entry)
{
    ej_ip_t ip_address;
    if (xml_parse_ip(NULL, 0, 0, -1, (const unsigned char*)logon_entry.ip_.c_str(), &ip_address) < 0) {
        throw EjudgeLogonServiceException("Ejudge socket connections error");
    }

    int uid, priv_level_out = 0;
    ej_cookie_t sid;
    unsigned char *name;

    int ret = userlist_clnt_priv_login(
            clnt,
            ULS_PRIV_CHECK_USER,
            ip_address,
            0,
            logon_entry.contest_id_,
            1, 
            5, // role
            (const unsigned char*)logon_entry.login_.c_str(),
            (const unsigned char*)logon_entry.password_.c_str(),
            &uid,
            &sid,
            &priv_level_out,
            &name
    );
    
    if (ret < 0) {
        char tmp[10];
        sprintf(tmp, "%d", ret);
        std::string err = "userlist_clnt_login: " + std::string(tmp);
        return LogonResult(err, 0, "");
    }
    
    char buf[256];
    sprintf(buf, "%llx", sid);
    LogonResult res(std::string((char *)name), uid, buf);

    return res;
}
